package com.bonshotels.DomainLayer.DomainModel;

import java.io.Serializable;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.HashSet;

/**
 * Classe Hotel.
 * 
 * Aquesta classe representa lentitat hotel amb tota la seva informacio. La classe es 
 * public ja que si no no es podria veure des del paquet DomainContollers, i precisament
 * el controlador CUReservarHabitacio necessita poder veure la classe Hotel. No obstant,
 * els metodes shan deixat amb la visibilitat per defecte ja que en java, la visibilitat 
 * per defecte restringeix que no es pugui cridar el metode des de fora del paquet (excepte
 * en el metode seleccionaHabitacio que lha de poder cridar el controlador cas dus que esta
 * en un altre paquet, i sha deixat en public). Com que java no te tuples hem implementat 
 * les tuples del disseny amb un Map (Diccionari) on la clau es de tipus String, i es 
 * correspon amb el nom de la variable de la tupla, i el valor es de tipus Object i per tant 
 * ens permet posar-hi qualsevol cosa. Per obtenir les dades de la tupla cal fer un get del 
 * valor passant-hi la clau (el nom de la variable) i fer un downcasting (conversio de tipus 
 * baixant en la jerarquia de classes) a la subclasse que ens interessa.
 * Com que utilitzem el framework de persistencia
 * Hibernate i aquest utilitza reflexio per fer la seva feina les nostres classes han d'acomplir una
 * serie de propietats perque la reflexio del Hibernate pugui fer completament la seva feina, les
 * classes han de ser publiques, han de tenir un constructor public buit (si no en tenen un d'explicit
 * el per defecte ja ho es), han d'implementar Serializable, i han de tenir substituits (override) els
 * metodes equals() i hashCode() perque identifiquin les classes a partir de la seva clau natural.
 * Com que Hotel s'identifica pel seu nom l'hem fet servir a l'hora
 * d'implementar equals(), hashCode() i els mapping de Hibernate.
 * @author Jordi Sabater
 */
public class Hotel implements Serializable {

	/** La constant serialVersionUID. */
	private static final long serialVersionUID = -4168806517894427665L;

	/** Nom del hotel. */
	private String nom;
	
	/** Descripcio del hotel. */
	private String descripcio;
	
	/** Un hotel te una associacio navegable cap a CategoriaHotel. Un hotel sempre 
	 * te una CategoriaHotel valida associada. Aquesta restriccio es responsabilitat 
	 * de la capa de dades i per aixo no es comprova en cap operacio daquesta classe.  */
	private CategoriaHotel categoriaHotel;
	
	/** Un hotel te una associacio multiple navegable cap a PreuTipusHabitacio. Aixo 
	 * es tradueix en un set de PreuTipusHabitacio. Un hotel sempre te com a minim un 
	 * PreuTipusHabitacio valid associat. Aquesta restriccio es responsabilitat de la 
	 * capa de dades i per aixo no es comprova en cap operacio daquesta classe. */
	private Set<PreuTipusHabitacio> preuTipusHabitacio;
	
	/** Un hotel te una associacio multiple navegable cap a Habitacio. Aixo es tradueix
	 * en un set de Habitacio. Un hotel sempre te com a minim tres Habitacio valides 
	 * associades. Aquesta restriccio es responsabilitat de la capa de dades i per aixo 
	 * no es comprova en cap operacio daquesta classe. */
	private Set<Habitacio> habitacio;
	
	/** Un hotel te una associacio multiple navegable cap a Comentari. Aixo es tradueix 
	 * en un set de Comentari. */
	private Set<Comentari> comentari;
	
	/**
	 * Instancia un nou hotel.
	 */
	public Hotel() {
		preuTipusHabitacio = new HashSet<PreuTipusHabitacio>();
		habitacio = new HashSet<Habitacio>();
		comentari = new HashSet<Comentari>();
	}
	
	/**
	 * Obte les habitacions disponibles del hotel per a la data inicial, data final i
	 * numero docupants que proporciona com a parametres qui fa la crida a loperacio.
	 * En la tupla que retorna (hem decidit fer servir els Map com a tupla) tambe hi ha
	 * informacio del hotel. Concretament el nom, la descripcio, la categoria i la seva 
	 * avaluacio mitjana. Hi ha un petit canvi respecte al disseny inicial provocat pel
	 * llenguatge que hem escollit per a implementar el disseny, i es que el llenguatge
	 * java no permet pasar parametres dentrada/sortida o de sortida. Es el cas de 
	 * loperacio que utilitzem per obtenir el tipus duna habitacio concreta, el seu preu 
	 * i el numero dhabitacions disponibles daquell tipus per a les restriccions de dates
	 * i numero docupants. En el disseny retornava un boolea i passavem dos parametres de
	 * sortida. Ara, com que no existeixen parametres de sortida en java hem hagut de fer
	 * que loperacio retorna una tupla (Map) que es diu resultatObteDadesTipusHabitacio
	 * amb els tres camps nomTipus, preuTipus i hiCaben. A mes a mes vam cometre un error
	 * en el disseny inicial i es que aquest boolea hiCaben que en cas de ser fals hauria
	 * de devitar fer treball innecessari no feiem la seva comprovacio. En la implementacio
	 * hem areglat aquest petit error que nomes es transforma en una condicio-if mes.
	 *
	 * @param dIni Data inicial de la reserva.
	 * @param dFi Data final de la reserva.
	 * @param numOcup Numero de persones per al que es busquen habitacions amb capacitat 
	 * igual o mes gran.
	 * @return Una tupla (Map) amb les dades del hotel (nom (key: "nomHotel"), descripcio
	 * (key: "desc"), categoria (key: "categoria") i avaluacio mitjana (key: "avaluacio")) 
	 * juntament amb un conjunt (key: "habs") de tipus dhabitacions del hotel que per als 
	 * parametres dIni, dFi i numOcup tenen un numero dhabiacions disponibles major que zero. 
	 * Per cada tipus dhabitacio del conjunt es pot consultar el seu nom (key: "tipusHab"), 
	 * el numero dhabitacions disponibles daquell tipus per a les dates i nomOcup desitjats
	 * (key: "numeroDisp"), i el preu (key: "preu").
	 */
	Map<String, Object> buscarHabitacio(Date dIni, Date dFi, int numOcup) {
		Set<Map<String, Object>> habsHot = new HashSet<Map<String, Object> >();	
		for(PreuTipusHabitacio pth : preuTipusHabitacio) {
			Map<String, Object> resultatObteDadesTipusHabitacio = 
			  pth.obteDadesTipusHabitacio(numOcup);
			boolean hiCaben = (Boolean) resultatObteDadesTipusHabitacio.get("hiCaben");
			if(hiCaben) {
				String tipusHab = (String) resultatObteDadesTipusHabitacio.get("nomTipus");
				int numDisp = numHabDisp(tipusHab, dIni, dFi);
				if(numDisp > 0) {
					float preuTipus = (Float) resultatObteDadesTipusHabitacio.get("preuTipus");
					Map<String, Object> tuplaHabsHot = new TreeMap<String, Object> ();
					tuplaHabsHot.put("tipusHab", tipusHab);
					tuplaHabsHot.put("numeroDisp", numDisp);
					tuplaHabsHot.put("preu", preuTipus);
					habsHot.add(tuplaHabsHot);
				}
			}
		}
		
		Map<String, Object> hotel = new TreeMap<String, Object> ();
		hotel.put("habs", habsHot);
		if(habsHot.size() > 0) {
			String nomCategoria = obteCategoria();
			float avaluacioMitjana = obteAvaluacioMitjana();
			hotel.put("nomHotel", nom);
			hotel.put("desc", descripcio);
			hotel.put("categoria", nomCategoria);
			hotel.put("avaluacio", avaluacioMitjana);
		}
		return hotel;
	}
	
	/**
	 * Obte el numero dhabitacions amb tipus 'nomTipus' del hotel que estan completament 
	 * disponibles per una franja temporal determinada ('dIni' a 'dFi'). 
	 *
	 * @param nomTipus Nom del tipus dhabitacio.
	 * @param dIni Data inicial de la franja temporal en la que es volen buscar habitacions
	 * disponibles.
	 * @param dFi Data final de la franja temporal en la que es volen buscar habitacions
	 * disponibles.
	 * @return Numero dhabitacions del hotel disponibles per a la franja temporal limitada 
	 * per 'dIni' i 'dFi' i que son de tipus 'nomTipus'.
	 */
	int numHabDisp(String nomTipus, Date dIni, Date dFi) {
		int numDisp = 0;
		for(Habitacio h : habitacio) {
			boolean esTipusIDisponible = h.tipusIDisponible(nomTipus, dIni, dFi);
			if(esTipusIDisponible) {
				++numDisp;
			}
		}
		return numDisp;
	}
	
	/**
	 * Obte la avaluacio mitjana de tots els comentaris del hotel. La interficie que 
	 * implementa per defecte la classe Set, Iterable, fa que no haguem de comprovar
	 * primer si el numero de comentaris es zero, ja que podria pasar que intentessim
	 * accedir al primer comentari i aquest no existis. No obstant la interficie 
	 * Iterator permet iterar per totes les instancies de Comentari que hi ha dins el
	 * Set comentari i en cas de no haver cap no accedira ni una vegada a linterior
	 * del bucle, de manera que no accedirem mai a una instancia nulla.
	 *
	 * @return La avaluacio mitjana de tots els comentaris del hotel.
	 */
	float obteAvaluacioMitjana() {
		float avaluacioMitjana = 0;
		if(comentari.size() > 0) {
			for(Comentari c : comentari) {
				int avaluacio = c.obteAvaluacio();
				avaluacioMitjana += avaluacio;
			}
			avaluacioMitjana /= comentari.size();
		}
		return avaluacioMitjana;
	}
	
	/**
	 * Obte el nom de la categoria del hotel.
	 *
	 * @return El nom de la categoria del hotel.
	 */
	public String obteCategoria() {
		String nomCategoria = categoriaHotel.obteNom();
		return nomCategoria;
	}
	
	/**
	 * Selecciona la primera habitacio lliure que troba que compleix que es de tipus
	 * 'tipusHab' i que esta completament lliure en la franja temporal que va des de
	 * 'dataInici' i 'dataFi' i obte la informacio de lhabitacio. Entre la informacio 
	 * que sobte es poden consultar el preu total de lhabitacio (dies compresos entre 
	 * 'dIni' i 'dFi' multiplicat pel preu diari de lhabitacio), i el seu numero 
	 * dhabitacio dins del hotel. Aqui vam cometre un petit error i es que la capsalera 
	 * de la funcio no coincideix la del diagrama de sequancies amb la del diagrama de 
	 * classes. La correcta es la que apareix en el diagrama de sequencies que es la 
	 * que implementem a continuacio. El primer bucle, com esta en el diagrama de
	 * sequencia, es per tota habitacio del hotel i mentre no trobat (!EsTipusIDisponible),
	 * aixo no seria necessari ja que la capa de presentacio ens garanteix que en accedir 
	 * a aquesta operacio sempre trobarem un tipus dhabitacio que compleix els requisits 
	 * i per tant nomes seria un blucle de tipus (mentre no trobat). No obstant si a mes 
	 * mirem que en cas que lelement que busquem no existeixi no fem cap acces erroni 
	 * perque sobrepassem el numero delements de habitacio el codi queda mas reusable. 
	 * El mateix passa amb el segon bucle i novament obtem per recomprobar que no 
	 * sobrepassem el numero delements, tot i que ja sabem que amb el nostra disseny 
	 * inicial aixo no passara mai, pero daquesta manera loperacio queda mes reusable i 
	 * possibilita que mes endavant algu pugui utilitzar aquest metode sense necessitat de 
	 * garantir que abans shagi comprovat que hi ha habitacions lliures daquell tipus. Com 
	 * que en java no existeixen els parametres de sortida, la crida al metode
	 * calculaPreuTipus que tenia el parametre 'trobat' de sortida, hem hagut de canviar
	 * una mica i fer que ara loperacio retorna una tupla (Map) amb el valor que retornava
	 * abans i el parametre 'trobat'. En la implementacio hem hagut dinicialitzar unes 
	 * variables a -1 ja que java es queixava de que potser no sinicialitzaven mai (que en
	 * aquest cas no seria veritat ja que son cerques en les que podem garantir que trobarem
	 * lobjecte que busquem).
	 *
	 * @param tipusHab Nom del tipus dhabitacio que es vol seleccionar.
	 * @param dataInici Data inicial per a la que es vol trobar una habitacio lliure.
	 * @param dataFi Data final per a la que es vol trobar una habitacio lliure.
	 * @return Una tupla (Map) amb el preu que comportaria reservar lhabitacio els dies
	 * compresos entre 'dataInici' i 'dataFi' (key: "preu") i el numero d'habitacio dins 
	 * del hotel que sha trobat i que esta lliure (key: "nHab"). La capa de presentacio 
	 * garanteix que sempre hi haura una disponible com a minim ja que abans haura cridat 
	 * a una altre operacio del cas d'us que nomes retorna aquells tipus d'habitacio del 
	 * hotel que en tenen alguna habitacio disponible per a reservar en la franja temporal 
	 * seleccionada.
	 */
	public Map<String, Object> seleccionaHabitacio(String tipusHab, Date dataInici, Date dataFi) {
		boolean esTipusIDisponible = false;
		int num = -1;
		Iterator<Habitacio> lih = habitacio.iterator();
		while(lih.hasNext() && !esTipusIDisponible) {
			Habitacio hab = lih.next();
			esTipusIDisponible = hab.tipusIDisponible(tipusHab,dataInici, dataFi);
			if(esTipusIDisponible) {
				num = hab.obteNumero();
			}
		}
		boolean trobat = false;
		float preuFinal = -1;
		Iterator<PreuTipusHabitacio> lip = preuTipusHabitacio.iterator();
		while(lip.hasNext() && !trobat) {
			PreuTipusHabitacio pth = lip.next();
			Map<String, Object> resultatCalculaPreuTipus = new TreeMap<String, Object> ();
			resultatCalculaPreuTipus = pth.calculaPreuTipus(tipusHab, dataInici, dataFi);
			trobat = (Boolean) resultatCalculaPreuTipus.get("trobat");	
			if(trobat) {
				preuFinal = (Float) resultatCalculaPreuTipus.get("preuFinal");
			}
		}
		Map<String, Object> infoHab = new TreeMap<String, Object> ();
		infoHab.put("preu", preuFinal);
		infoHab.put("nHab", num);
		return infoHab;
	}

	/**
	 * Getter de nom.
	 *
	 * @return El nom
	 */
	public String getNom() {
		return nom;
	}

	/**
	 * Setter de nom.
	 *
	 * @param nom El nou nom
	 */
	public void setNom(String nom) {
		this.nom = nom;
	}

	/**
	 * Getter de descripcio.
	 *
	 * @return La descripcio
	 */
	public String getDescripcio() {
		return descripcio;
	}

	/**
	 * Setter de descripcio.
	 *
	 * @param descripcio La nova descripcio
	 */
	public void setDescripcio(String descripcio) {
		this.descripcio = descripcio;
	}

	/**
	 * Getter de categoria hotel.
	 *
	 * @return La categoria hotel
	 */
	public CategoriaHotel getCategoriaHotel() {
		return categoriaHotel;
	}

	/**
	 * Setter de categoria hotel.
	 *
	 * @param categoriaHotel La nova categoria hotel
	 */
	public void setCategoriaHotel(CategoriaHotel categoriaHotel) {
		this.categoriaHotel = categoriaHotel;
	}

	/**
	 * Getter de preu tipus habitacio.
	 *
	 * @return El preu tipus habitacio
	 */
	public Set<PreuTipusHabitacio> getPreuTipusHabitacio() {
		return preuTipusHabitacio;
	}

	/**
	 * Setter de preu tipus habitacio.
	 *
	 * @param preuTipusHabitacio El nou preu tipus habitacio
	 */
	public void setPreuTipusHabitacio(Set<PreuTipusHabitacio> preuTipusHabitacio) {
		this.preuTipusHabitacio = preuTipusHabitacio;
	}

	/**
	 * Getter de habitacio.
	 *
	 * @return La habitacio
	 */
	public Set<Habitacio> getHabitacio() {
		return habitacio;
	}

	/**
	 * Setter de habitacio.
	 *
	 * @param habitacio La nova habitacio
	 */
	public void setHabitacio(Set<Habitacio> habitacio) {
		this.habitacio = habitacio;
	}

	/**
	 * Getter de comentari.
	 *
	 * @return El comentari
	 */
	public Set<Comentari> getComentari() {
		return comentari;
	}

	/**
	 * Setter de comentari.
	 *
	 * @param comentari El nou comentari
	 */
	public void setComentari(Set<Comentari> comentari) {
		this.comentari = comentari;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((nom == null) ? 0 : nom.hashCode());
		return result;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Hotel other = (Hotel) obj;
		if (nom == null) {
			if (other.nom != null)
				return false;
		} else if (!nom.equals(other.nom))
			return false;
		return true;
	}

	/**
	 * Getter de serialversionuid.
	 *
	 * @return El serialversionuid
	 */
	public static long getSerialversionuid() {
		return serialVersionUID;
	}
	
}
